package com.lzy.article.controller;

import com.lzy.api.controller.article.ArticleControllerApi;
import com.lzy.api.controller.article.ArticlePortalControllerApi;
import com.lzy.api.controller.user.BaseController;
import com.lzy.article.service.ArticlePortalService;
import com.lzy.article.service.ArticleService;
import com.lzy.enums.ArticleCoverType;
import com.lzy.enums.ArticleReviewStatus;
import com.lzy.enums.YesOrNo;
import com.lzy.grace.result.GraceJSONResult;
import com.lzy.grace.result.ResponseStatusEnum;
import com.lzy.pojo.Article;
import com.lzy.pojo.Category;
import com.lzy.pojo.bo.NewArticleBO;
import com.lzy.pojo.eo.ArticleEO;
import com.lzy.pojo.vo.AppUserVO;
import com.lzy.pojo.vo.ArticleDetailVO;
import com.lzy.pojo.vo.IndexArticleVO;
import com.lzy.utils.IPUtil;
import com.lzy.utils.JsonUtils;
import com.lzy.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@RestController
public class ArticlePortalController extends BaseController implements ArticlePortalControllerApi {

    final static Logger logger = LoggerFactory.getLogger(ArticlePortalController.class);

    @Autowired
    private ArticlePortalService articlePortalService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Override
    public GraceJSONResult eslist(String keyword,
                                  Integer category,
                                  Integer page,
                                  Integer pageSize) {

        /**
         * es查询：
         *      1. 首页默认查询，不带参数
         *      2. 按照文章分类查询
         *      3. 按照关键字查询
         */

        // es的页面是从0开始计算的，所以在这里page需要-1
        if (page < 1) return null;
        page--;
        Pageable pageable = PageRequest.of(page, pageSize);


        SearchQuery query = null;
        AggregatedPage<ArticleEO> pagedArticle = null;
        // 符合第1种情况
        if (StringUtils.isBlank(keyword) && category == null) {
            query = new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchAllQuery())
                    .withPageable(pageable)
                    .build();
            pagedArticle = esTemplate.queryForPage(query, ArticleEO.class);
        }

        // 符合第2种情况
        if (StringUtils.isBlank(keyword) && category != null) {
            query = new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.termQuery("categoryId", category))
                    .withPageable(pageable)
                    .build();
            pagedArticle = esTemplate.queryForPage(query, ArticleEO.class);
        }


        // 基于第3种情况的关键字搜索高亮展示
        String searchTitleField = "title";
        if (StringUtils.isNotBlank(keyword) && category == null) {
            String preTag = "<font color='red'>";
            String postTag = "</font>";
            query = new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchQuery(searchTitleField, keyword))
                    .withHighlightFields(new HighlightBuilder.Field(searchTitleField)
                            .preTags(preTag)
                            .postTags(postTag)
                    )
                    .withPageable(pageable)
                    .build();

            pagedArticle = esTemplate.queryForPage(query, ArticleEO.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {

                    List<ArticleEO> articleHighLightList = new ArrayList<>();
                    SearchHits hits = response.getHits();
                    for (SearchHit h : hits) {
                        HighlightField highlightField = h.getHighlightFields().get(searchTitleField);
                        String title = highlightField.getFragments()[0].toString();

                        // 获得其他的字段信息数据，并且重新封装
                        String articleId = (String)h.getSourceAsMap().get("id");
                        Integer categoryId = (Integer)h.getSourceAsMap().get("categoryId");
                        Integer articleType = (Integer)h.getSourceAsMap().get("articleType");
                        String articleCover = (String)h.getSourceAsMap().get("articleCover");
                        String publishUserId = (String)h.getSourceAsMap().get("publishUserId");
                        Long dateLong = (Long)h.getSourceAsMap().get("publishTime");
                        Date publishTime = new Date(dateLong);

                        ArticleEO articleEO = new ArticleEO();
                        articleEO.setId(articleId);
                        articleEO.setCategoryId(categoryId);
                        articleEO.setTitle(title);
                        articleEO.setArticleType(articleType);
                        articleEO.setArticleCover(articleCover);
                        articleEO.setPublishUserId(publishUserId);
                        articleEO.setPublishTime(publishTime);

                        articleHighLightList.add(articleEO);
                    }

                    // 如果使用es7的话，这部分的代码会精简很多

                    return new AggregatedPageImpl<>((List<T>)articleHighLightList,
                            pageable,
                            response.getHits().totalHits);
                }

                @Override
                public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                    return null;
                }
            });
        }


        // 重组文章列表

        List<ArticleEO> articEOleList = pagedArticle.getContent();
        List<Article> articleList = new ArrayList<>();
        for (ArticleEO a : articEOleList) {
//            System.out.println(a);
            Article article = new Article();
            BeanUtils.copyProperties(a, article);
            articleList.add(article);
        }

        // 重新封装成之前的grid格式
        PagedGridResult gridResult = new PagedGridResult();
        gridResult.setRows(articleList);
        gridResult.setPage(page + 1);
        gridResult.setTotal(pagedArticle.getTotalPages());
        gridResult.setRecords(pagedArticle.getTotalElements());

        gridResult = rebuildArticleGrid(gridResult);

        return GraceJSONResult.ok(gridResult);
    }


    @Override
    public GraceJSONResult list(String keyword, Integer category, Integer page, Integer pageSize) {

        if (page == null) {
            page = COMMON_START_PAGE;
        }

        if (pageSize == null) {
            pageSize = COMMON_PAGE_SIZE;
        }

        PagedGridResult gridResult = articlePortalService.queryIndexArticleList(keyword, category, page, pageSize);

        gridResult = rebuildArticleGrid(gridResult);
        return GraceJSONResult.ok(gridResult);
    }

    /**
     * 从publisherList中获得匹配的用户
     * @param publisherId
     * @param publisherList
     * @return
     */
    private AppUserVO getUserIfEqualPublisher(String publisherId,
                                              List<AppUserVO> publisherList) {
        for (AppUserVO u : publisherList) {
            if (u.getId().equalsIgnoreCase(publisherId)) {
                return u;
            }
        }
        return null;
    }


    @Override
    public GraceJSONResult hotList() {
        return GraceJSONResult.ok(articlePortalService.queryHotArticleList());
    }


    @Override
    public GraceJSONResult queryArticleListOfWriter(String writerId, Integer page, Integer pageSize) {

        System.out.println("writerId=" + writerId);

        if (page == null) {
            page = COMMON_START_PAGE;
        }

        if (pageSize == null) {
            pageSize = COMMON_PAGE_SIZE;
        }

        PagedGridResult gridResult = articlePortalService.queryArticleListOfWriter(writerId, page, pageSize);
        gridResult = rebuildArticleGrid(gridResult);
        return GraceJSONResult.ok(gridResult);
    }

    @Override
    public GraceJSONResult queryGoodArticleListOfWriter(String writerId) {
        PagedGridResult gridResult = articlePortalService.queryGoodArticleListOfWriter(writerId);
        gridResult = rebuildArticleGrid(gridResult);
        return GraceJSONResult.ok(gridResult);
    }

    @Override
    public GraceJSONResult detail(String articleId) {
        // 查询文章内容
        ArticleDetailVO article = articlePortalService.detail(articleId);

        // 1. 获取发布者id
        Set<String> idSet = new HashSet<>();
        idSet.add(article.getPublishUserId());
        List<AppUserVO> publisherList = getPublisherList(idSet);

        // 2. 获取发布者的个人信息
        if(publisherList.isEmpty()) {
            return GraceJSONResult.ok(article);
        }
        AppUserVO vo = getUserIfEqualPublisher(article.getPublishUserId(),publisherList);
        article.setPublishUserName(vo.getNickname());
        article.setReadCounts(getCountsFromRedis(REDIS_ARTICLE_READ_COUNTS + ":" + articleId));
        return GraceJSONResult.ok(article);
    }

    @Override
    public Integer readCounts(String articleId) {
        return getCountsFromRedis(REDIS_ARTICLE_READ_COUNTS + ":" + articleId);
    }


    @Override
    public GraceJSONResult readArticle(String articleId, HttpServletRequest request) {

        String userIP = IPUtil.getRequestIp(request);
         // 1. 设置永久存在key，表示该文章当前ip已经阅读过了，无法累加阅读量
        redis.setnx(REDIS_ALREADY_READ + ":" +  articleId + ":" + userIP, userIP);

         // 2. redis 文章阅读数累加
        redis.increment(REDIS_ARTICLE_READ_COUNTS + ":" + articleId, 1);

        redis.zincrby("articleSet",1,articleId);
        return GraceJSONResult.ok();
    }


    public PagedGridResult rebuildArticleGrid(PagedGridResult gridResult){
        // START
        List<Article> list = (List<Article>) gridResult.getRows();

        // 1. 构建发布者id的列表
        Set<String> idSet = new HashSet<>();
        List<String> idList = new ArrayList<>();
        for (Article article : list) {
            //System.out.println(article.getPublishUserId());
            // 1.1 构建发布者的set
            idSet.add(article.getPublishUserId());
            // 1.2 构建文件id的list
            idList.add(REDIS_ARTICLE_READ_COUNTS + ":" + article.getId());
        }

        // 2. 发起redis的mget批量查询api，获取对应的值
        List<String> readCountsRedisList = redis.mget(idList);

        // 3. 发起远程调用（restTemplate），请求用户服务获取用户列表
        List<AppUserVO> publisherList = getPublisherList(idSet);


        // 4. 拼接两个list，重组文章列表
        List<IndexArticleVO> indexArticleVOList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            IndexArticleVO indexArticleVO = new IndexArticleVO();
            Article article = list.get(i);

            BeanUtils.copyProperties(article,indexArticleVO);

            // 4.1 从publicerList获取发布者的信息
            AppUserVO vo = getUserIfEqualPublisher(article.getPublishUserId(),publisherList);
            indexArticleVO.setPublisherVO(vo);
            // 4.2 从redis中获取每篇文章的阅读数量
            String redisCountsStr = readCountsRedisList.get(i);
            int readCounts = 0;
            if(StringUtils.isNotBlank(redisCountsStr)){
                readCounts = Integer.valueOf(redisCountsStr);
            }
            indexArticleVO.setReadCounts(readCounts);


            indexArticleVOList.add(indexArticleVO);
        }


        gridResult.setRows(indexArticleVOList);
        // END
        return gridResult;
    }
}

